// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"bytes"
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"
)

// endsWithCSSKeyword报告b是否以一个标识结尾，
// case与小写kw不敏感地匹配。
func endsWithCSSKeyword(b []byte, kw string) bool {
	i := len(b) - len(kw)
	if i < 0 {
		// 太短了。
		return false
	}
	if i != 0 {
		r, _ := utf8.DecodeLastRune(b[:i])
		if isCSSNmchar(r) {
			// 太长了。
			return false
		}
	}
	// 许多CSS关键字，例如“！important”可以有编码字符，
	// 但是根据
	// https:
	// URI产品不允许这样做。这不会尝试识别编码的关键字。例如，
	// 给定“\75\72\6c”和“url”这个返回值为false。
	return string(bytes.ToLower(b[i:])) == kw
}

// isCSSNmchar报告CSS标识符中的任何位置是否允许使用符文。
func isCSSNmchar(r rune) bool {
	// 基于CSS3 nmchar生成，但忽略多符文转义
	// 序列。
	// https:
	return 'a' <= r && r <= 'z' ||
		'A' <= r && r <= 'Z' ||
		'0' <= r && r <= '9' ||
		r == '-' ||
		r == '_' ||
		// 下面是非ASCII大小写。
		0x80 <= r && r <= 0xd7ff ||
		0xe000 <= r && r <= 0xfffd ||
		0x10000 <= r && r <= 0x10ffff
}

// decodeCSS在给定字符串序列的情况下对CSS3转义进行解码。
// 如果没有更改，则返回输入，否则返回一个新数组支持的切片
// 。
// https:
func decodeCSS(s []byte) []byte {
	i := bytes.IndexByte(s, '\\')
	if i == -1 {
		return s
	}
	// 一个码点的UTF-8序列的长度永远不会超过1+
	// 表示该码点所需的十六进制数，因此len（s）是输出长度的上限。
	b := make([]byte, 0, len(s))
	for len(s) != 0 {
		i := bytes.IndexByte(s, '\\')
		if i == -1 {
			i = len(s)
		}
		b, s = append(b, s[:i]...), s[i:]
		if len(s) < 2 {
			break
		}
		// https:
		// escape:：=unicode |'\[#x20-#x7E#x80-#xD7FF#xE000-#xffd#x10000-#x10FFFF]
		if isHex(s[1]) {
			// unicode:='0-9a-fA F]{1,6}wc？
			j := 2
			for j < len(s) && j < 7 && isHex(s[j]) {
				j++
			}
			r := hexDecode(s[1:j])
			if r > unicode.MaxRune {
				r, j = r/16, j-1
			}
			n := utf8.EncodeRune(b[len(b):cap(b)], r)
			// 末尾的可选空格允许十六进制
			// 序列后面跟一个文本十六进制。
			// string（decodeCSS（[]字节（`\A B`））==“\nB”
			b, s = b[:len(b)+n], skipCSSSpace(s[j:])
		} else {
			// /\\`解码为``和``到```”。
			_, n := utf8.DecodeRune(s[1:])
			b, s = append(b, s[1:1+n]...), s[1+n:]
		}
	}
	return b
}

// isHex报告给定字符是否为十六进制数字。
func isHex(c byte) bool {
	return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
}

// hexDecode解码一个短的十六进制数字序列：“10”->16。
func hexDecode(s []byte) rune {
	n := '\x00'
	for _, c := range s {
		n <<= 4
		switch {
		case '0' <= c && c <= '9':
			n |= rune(c - '0')
		case 'a' <= c && c <= 'f':
			n |= rune(c-'a') + 10
		case 'A' <= c && c <= 'F':
			n |= rune(c-'A') + 10
		default:
			panic(fmt.Sprintf("Bad hex digit in %q", s))
		}
	}
	return n
}

// skipcsspace返回后缀c，跳过单个空格。
func skipCSSSpace(c []byte) []byte {
	if len(c) == 0 {
		return c
	}
	// wc:：=#x9 | xA | xC | xD | x20 
	switch c[0] {
	case '\t', '\n', '\f', ' ':
		return c[1:]
	case '\r':
		// 这与CSS3的wc产品不同，因为它包含一个
		// 可能的规范错误，即wc包含nl（换行）中的所有单字节
		// 序列，但不包含CRLF。
		if len(c) >= 2 && c[1] == '\n' {
			return c[2:]
		}
		return c[1:]
	}
	return c
}

// iscsspace报告b是否是wc中定义的CSS空间字符。
func isCSSSpace(b byte) bool {
	switch b {
	case '\t', '\n', '\f', '\r', ' ':
		return true
	}
	return false
}

// cssEscaper使用\<hex>+转义符转义HTML和CSS特殊字符。
func cssEscaper(args ...any) string {
	s, _ := stringify(args...)
	var b strings.Builder
	r, w, written := rune(0), 0, 0
	for i := 0; i < len(s); i += w {
		// 参见htmlEscaper中的评论。
		r, w = utf8.DecodeRuneInString(s[i:])
		var repl string
		switch {
		case int(r) < len(cssReplacementTable) && cssReplacementTable[r] != "":
			repl = cssReplacementTable[r]
		default:
			continue
		}
		if written == 0 {
			b.Grow(len(s))
		}
		b.WriteString(s[written:i])
		b.WriteString(repl)
		written = i + w
		if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) {
			b.WriteByte(' ')
		}
	}
	if written == 0 {
		return s
	}
	b.WriteString(s[written:])
	return b.String()
}

var cssReplacementTable = []string{
	0:    `\0`,
	'\t': `\9`,
	'\n': `\a`,
	'\f': `\c`,
	'\r': `\d`,
	// 将HTML特例编码为十六进制，这样输出就可以嵌入到HTML属性中，而无需进一步编码。
	'"':  `\22`,
	'&':  `\26`,
	'\'': `\27`,
	'(':  `\28`,
	')':  `\29`,
	'+':  `\2b`,
	'/':  `\2f`,
	':':  `\3a`,
	';':  `\3b`,
	'<':  `\3c`,
	'>':  `\3e`,
	'\\': `\\`,
	'{':  `\7b`,
	'}':  `\7d`,
}

var expressionBytes = []byte("expression")
var mozBindingBytes = []byte("mozbinding")

// cssValueFilter允许在输出中使用无害的CSS值，包括CSS 
// 数量（10px或25%）、ID或类文本（#foo、.bar）、关键字值
// （继承，蓝色）和颜色（#888）。
// 它过滤掉不安全的值，例如那些影响令牌边界的值、
// 以及任何可能执行脚本的值。
func cssValueFilter(args ...any) string {
	s, t := stringify(args...)
	if t == contentTypeCSS {
		return s
	}
	b, id := decodeCSS([]byte(s)), make([]byte, 0, 64)

	// CSS3错误处理根据
	// https:
	// 格式错误的声明指定为遵守字符串边界。用户代理必须通过读取直到
	// 标记，同时遵守
	// 声明结束来处理解析声明时遇到的意外
	// 规则匹配（）、[]、{}、“”和“”，并正确处理
	// 转义。例如，格式错误的声明可能缺少
	// 属性、冒号（：）或值。
	// 所以我们需要确保值中没有不匹配的括号
	// 或引号字符，以防止浏览器在可能嵌入JavaScript源代码的字符串中重新解析
	// 。
	for i, c := range b {
		switch c {
		case 0, '"', '\'', '(', ')', '/', ';', '@', '[', '\\', ']', '`', '{', '}':
			return filterFailsafe
		case '-':
			// 不允许<！——或者-->。wen jian defg
			if i != 0 && b[i-1] == '-' {
				return filterFailsafe
			}
		default:
			if c < utf8.RuneSelf && isCSSNmchar(rune(c)) {
				id = append(id, c)
			}
		}
	}
	id = bytes.ToLower(id)
	if bytes.Contains(id, expressionBytes) || bytes.Contains(id, mozBindingBytes) {
		return filterFailsafe
	}
	return string(b)
}
